#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/fcntl.h>
#include <linux/soundcard.h>
#include <pthread.h>
#include "mmio_plugin.h"
#include "sound_config.h"
#define USER_MEM_LEN (2 * SOUND_CARD_MEM_LEN + 3 * sizeof(int)) // int1: output to device or file, int2: cache pos, int3: rx_ring pos
#define REG_MEM_OFFSET 0x0
#define TX_RING_MEM_OFFSET (REG_MEM_OFFSET + REGS_SIZE * sizeof(int))
#define RX_RING_MEM_OFFSET (TX_RING_MEM_OFFSET + sizeof(desc) * RINGS_SIZE)
#define TO64(x) (*((uint64 *)(x)))
#define TO32(x) (*((uint32 *)(x)))
#define TO16(x) (*((uint16 *)(x)))
#define TO8(x)  (*((uint8 *)(x)))

// gcc -shared -o sim.so -Wall -Werror -fPIC sim.c
// ~/riscv-isa-sim/spike --extlib=sound_card_sim/sim.so --device=sound_card_sim,0x10000000 obj/thinos

static void *pthread(void *self) {
    desc *t = (desc *)((char *)self + TX_RING_MEM_OFFSET);
    char *base = (char *)self;
    uint32 *r = (uint32 *)self;
    for (;;) {
        if (t[(r[0] - 1 + RINGS_SIZE) % RINGS_SIZE].status == ! RING_READY) {
            // 如果检测到当前有充满数据的tx_ring，则经过一段延迟后一口气全复制到rx_ring
            sleep(2);
            int f;
            if (TO32(base + USER_MEM_LEN - 3 * sizeof(int))) {
                f = open("/dev/dsp", O_WRONLY);
                int bit = 16, rate = 20000, channel = 2;
                ioctl(f, SOUND_PCM_WRITE_BITS, &bit);
                ioctl(f, SOUND_PCM_WRITE_RATE, &rate);
                ioctl(f, SOUND_PCM_WRITE_CHANNELS, &channel);
            } else {
                f = open("sound_card_sim/dsp.wav", O_WRONLY | O_APPEND);
            }
            write(f, (char *)self + SOUND_CARD_MEM_LEN, TO32(base + USER_MEM_LEN - 2 * sizeof(uint32)));
            close(f);
            // 播放完就可以清空cache了
            TO32(base + USER_MEM_LEN - 2 * sizeof(uint32)) = 0;
            // 依次复制到rx_ring
            for (int i = 0; i < RINGS_SIZE; i++)
                if (t[i].status == !RING_READY) {
                    int rx_current = TO32(base + USER_MEM_LEN - sizeof(uint32))++; // 当前rx环的索引
                    struct desc *rx_ring = (struct desc *)(base + RX_RING_MEM_OFFSET) + rx_current;

                    if (rx_ring->status == RING_READY) printf("Recieve too slow"); // 写入环全部用完了
                    memcpy(rx_ring, t + i, sizeof(desc));
                    rx_ring->status = RING_READY; t[i].status = RING_READY; // 表示该rx环可以被读取,tx环处理结束
                }
        }
        sleep(2);
    }
    return NULL;
}

void* sound_card_sim_alloc(const char* args) {
    char *r = malloc(USER_MEM_LEN);
    memset(r, 0, USER_MEM_LEN);
    TO32(r + REG_MEM_OFFSET + sizeof(int)) = RINGS_SIZE - 1;
    desc *t = (desc *)(r + TX_RING_MEM_OFFSET);
    for (int i = 0; i < RINGS_SIZE; i++) t[i].status = RING_READY;
    pthread_t p; pthread_create(&p, NULL, pthread, r);
    TO32(r + USER_MEM_LEN - 3 * sizeof(int)) = args[0] == '1';
    return (void *)r;
}

bool sound_card_sim_load(void* self, reg_t addr, size_t len, uint8* bytes) {
    if (addr >= SOUND_CARD_MEM_LEN) return true;
    char *off = (char *)self + addr;
    switch (len) {
        case 1:
            *bytes = *off;
            break;
        case 2:
            TO16(bytes) = TO16(off);
            break;
        case 4:
            TO32(bytes) = TO32(off);
            break;
        case 8:
            TO64(bytes) = TO64(off);
            break;
    }
    return true;
}

bool sound_card_sim_store(void* self, reg_t addr, size_t len, const uint8* bytes) {
    if (addr >= SOUND_CARD_MEM_LEN) return true;
    char *base = (char *)self;
    char *off = base + addr;
    // 当第一个寄存器的值发生变化,需要发送
    if (addr == REG_MEM_OFFSET) {
        uint64 old = TO32(off); TO32(off) = TO32(bytes); // 先更新值防止出错

        struct desc *tx_ring = (struct desc *)(base + TX_RING_MEM_OFFSET) + old;
        tx_ring->status = !RING_READY; // 表示该tx环在处理中
        // printf("device get tx_ring: %d %d\n", tx_ring->offset, tx_ring->length);
        // 把tx_ring里的数据复制到cache中
        char *cache = base + SOUND_CARD_MEM_LEN;
        memcpy(cache + TO32(base + USER_MEM_LEN - 2 * sizeof(uint32)), tx_ring->mbuf, tx_ring->length);
        TO32(base + USER_MEM_LEN - 2 * sizeof(uint32)) += tx_ring->length;
        // 该tx_ring仍然处于占用状态，需等另一个线程将tx_ring复制到rx_ring才能解除占用
        
    }
    switch (len) {
        case 1:
            *off = *bytes;
            break;
        case 2:
            TO16(off) = TO16(bytes);
            break;
        case 4:
            TO32(off) = TO32(bytes);
            break;
        case 8:
            TO64(off) = TO64(bytes);
            break;
    }
    return true;
}

void sound_card_sim_dealloc(void* self) {
    free(self);
}

__attribute__((constructor)) static void on_load()
{
  static mmio_plugin_t sound_card_sim_plugin = {
      sound_card_sim_alloc,
      sound_card_sim_load,
      sound_card_sim_store,
      sound_card_sim_dealloc
  };

  register_mmio_plugin("sound_card_sim", &sound_card_sim_plugin);
}
